#include <psp2/kernel/clib.h>
#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>

#include "config.h"
#include "utils.h"

const char taihen_config_recovery_header[] =
    "# This file is used as an alternative if ux0:tai/config.txt is not found.\n";

const char taihen_config_header[] =
    "# For users plugins, you must refresh taiHEN from HENkaku Settings for\n"
    "# changes to take place.\n"
    "# For kernel plugins, you must reboot for changes to take place.\n";

const char taihen_config[] =
    "*KERNEL\n"
    "# henkaku.skprx is hard-coded to load and is not listed here\n"
    "*main\n"
    "# main is a special titleid for SceShell\n"
    "ur0:tai/henkaku.suprx\n"
    "*NPXS10015\n"
    "# this is for modifying the version string\n"
    "ur0:tai/henkaku.suprx\n"
    "*NPXS10016\n"
    "# this is for modifying the version string in settings widget\n"
    "ur0:tai/henkaku.suprx\n";

#define MAX_PATH_LENGTH 1024
#define MAX_NAME_LENGTH 256

#define BUFFER_SIZE (1024 * 1024)
#define GAMMESD_INSTALL_PATH "ur0:tai/gamesd.skprx"

extern unsigned char _binary_res_gamesd_skprx_start;
extern unsigned char _binary_res_gamesd_skprx_size;

char config_buffer[BUFFER_SIZE];

int make_file_name(char *dst_name, const char *src_path, int size)
{
    int i;
    int sep_ind = -1;
    int len = sceClibStrnlen(src_path, MAX_NAME_LENGTH);

    if (len <= 0)
        return -1;

    if (src_path[len - 1] == '/' || src_path[len - 1] == ':')
        return -1;

    for (i = len - 1; i >= 0; i--)
    {
        if (src_path[i] == '/' || src_path[i] == ':')
        {
            sep_ind = i;
            break;
        }
    }
    if (sep_ind == -1)
        return -1;

    int new_len = len - (sep_ind + 1);
    if (new_len > size - 1)
        new_len = size - 1;
    if (new_len > 0)
        sceClibStrncpy(dst_name, src_path + (sep_ind + 1), new_len);
    dst_name[new_len] = '\0';

    return 0;
}

int write_taihen_config(const char *path, int recovery)
{
    int fd;

    // write default config
    sceIoRemove(path);
    fd = sceIoOpen(path, SCE_O_TRUNC | SCE_O_CREAT | SCE_O_WRONLY, 6);
    if (recovery)
    {
        sceIoWrite(fd, taihen_config_recovery_header, sizeof(taihen_config_recovery_header) - 1);
    }
    sceIoWrite(fd, taihen_config_header, sizeof(taihen_config_header) - 1);
    sceIoWrite(fd, taihen_config, sizeof(taihen_config) - 1);
    sceIoClose(fd);

    return 0;
}

int reset_taihen_config()
{
    sceIoMkdir("ux0:tai", 6);
    sceIoMkdir("ur0:tai", 6);

    write_taihen_config("ux0:tai/config.txt", 0);
    write_taihen_config("ur0:tai/config.txt", 1);

    return 0;
}

int get_line_length(const char *buf, int size)
{
    int i = 0;
    uint8_t *p = (uint8_t *)buf;

    for (i = 0; i < size; i++)
    {
        if (p[i] < 0x20 && p[i] != '\t')
        {
            i++;
            break;
        }
    }

    return i;
}

int modify_tai_config(char *buffer, int size, char *location, char *path)
{
    int location_len = sceClibStrnlen(location, MAX_PATH_LENGTH);
    int path_len = sceClibStrnlen(path, MAX_PATH_LENGTH);
    int new_size = size;
    int remaining_size = size;
    char *location_add_pointer = buffer;
    char *path_add_pointer = buffer;
    char *p = buffer;
    int location_geted = 0;
    int location_add_pointer_decided = 0;
    int path_add_pointer_decided = 0;
    int is_kernel_location = 0;

    if (sceClibStrncasecmp(location, "*KERNEL", location_len) == 0)
        is_kernel_location = 1;

    // Skip UTF-8 bom
    uint32_t bom = 0xBFBBEF;
    if (sceClibStrncmp(p, (char *)&bom, 3) == 0)
    {
        p += 3;
        remaining_size -= 3;
    }

    char name[MAX_NAME_LENGTH];
    make_file_name(name, path, MAX_NAME_LENGTH);
    int name_len = sceClibStrnlen(name, MAX_NAME_LENGTH);
    char tmp_name[MAX_NAME_LENGTH];
    int tmp_name_len;

    char *line;
    int ori_len, len;

    while (remaining_size > 0)
    {
        ori_len = get_line_length(p, remaining_size);
        if (ori_len <= 0)
            break;

        remaining_size -= ori_len;
        line = p;
        len = ori_len;

        // Trim string
        while (*((unsigned char *)line) <= 0x20)
        {
            line++;
            len--;
        }
        while (len > 0 && ((unsigned char *)line)[len - 1] <= 0x20)
        {
            len--;
        }

        int is_path_line = 1;

        if (*((unsigned char *)line) == '*')
        {
            is_path_line = 0;

            if (is_kernel_location && !location_add_pointer_decided)
            {
                location_add_pointer_decided = 1;
                location_add_pointer = p;
            }
        }
        if (*((unsigned char *)line) == '#' || len <= 0)
        {
            p += ori_len;
            if (location_geted && !path_add_pointer_decided)
                path_add_pointer = p;
            continue;
        }
        else if (location_geted && !path_add_pointer_decided)
        {
            path_add_pointer_decided = 1;
            path_add_pointer = p;
        }

        if (is_path_line)
        {
            char ch = *(line + len);
            *(line + len) = '\0';
            make_file_name(tmp_name, line, MAX_NAME_LENGTH);
            *(line + len) = ch;
            tmp_name_len = sceClibStrnlen(tmp_name, MAX_NAME_LENGTH);
        }

        if (is_path_line && tmp_name_len == name_len && sceClibStrncasecmp(tmp_name, name, name_len) == 0)
        {
            char *dst = p;
            char *src = p + ori_len;
            sceClibMemmove(dst, src, remaining_size); // memmove <<
            new_size += (dst - src);
        }
        else
        {
            p += ori_len;

            if (!is_path_line && !location_geted && (len == location_len && sceClibStrncasecmp(line, location, len) == 0))
            {
                location_geted = 1;
                path_add_pointer = p;
            }
        }
    }

    if (!location_geted)
    { // Add location
        if (is_kernel_location)
        {
            char *dst = location_add_pointer + location_len + 1;
            char *src = location_add_pointer;
            int need_add_before_lb = 0;
            if (location_add_pointer > buffer && *(location_add_pointer - 1) != '\n')
            { // Add path
                need_add_before_lb = 1;
                dst += 1;
            }
            int move_len = buffer + new_size - src;
            sceClibMemmove(dst, src, move_len); // memmove >>
            if (need_add_before_lb)
            {
                sceClibMemcpy(location_add_pointer, "\n", 1);
                location_add_pointer += 1;
            }
        }
        else
        {
            location_add_pointer = buffer + new_size;
        }

        char *src = location_add_pointer;
        if (location_add_pointer > buffer && *(location_add_pointer - 1) != '\n')
        {
            sceClibMemcpy(location_add_pointer, "\n", 1);
            location_add_pointer += 1;
        }
        sceClibMemcpy(location_add_pointer, location, location_len);
        location_add_pointer += location_len;
        sceClibMemcpy(location_add_pointer, "\n", 1);
        location_add_pointer += 1;
        new_size += (location_add_pointer - src);
        path_add_pointer = location_add_pointer;
    }

    char *dst = path_add_pointer + path_len + 1;
    char *src = path_add_pointer;
    int need_add_before_lb = 0;
    if (path_add_pointer > buffer && *(path_add_pointer - 1) != '\n')
    { // Add path
        need_add_before_lb = 1;
        dst += 1;
    }
    int move_len = buffer + new_size - src;
    sceClibMemmove(dst, src, move_len); // memmove >>
    if (need_add_before_lb)
    {
        sceClibMemcpy(path_add_pointer, "\n", 1);
        path_add_pointer += 1;
    }
    sceClibMemcpy(path_add_pointer, path, path_len);
    path_add_pointer += path_len;
    sceClibMemcpy(path_add_pointer, "\n", 1);
    path_add_pointer += 1;
    new_size += (path_add_pointer - src);

    buffer[new_size] = '\0';
    return new_size;
}

int install_gamesd_plugin_by_path(const char *path)
{
    int size = read_file(path, config_buffer, BUFFER_SIZE - 1);
    if (size < 0)
        return size;
    config_buffer[size] = '\0';

    size = modify_tai_config(config_buffer, size, "*KERNEL", GAMMESD_INSTALL_PATH);

    return write_file(path, config_buffer, size);
}

int install_gamesd_plugin()
{
    char *paths[] = {
        "ux0:tai/config.txt",
        "ur0:tai/config.txt",
        "uma0:tai/config.txt",
        "imc0:tai/config.txt",
        "xmc0:tai/config.txt",
    };

    if (!file_exists("ur0:tai/config.txt"))
    {
        sceIoMkdir("ur0:tai", 6);
        write_taihen_config("ur0:tai/config.txt", 1);
    }

    int res = -1;
    int i;
    for (i = 0; i < sizeof(paths) / sizeof(char *); i++)
    {
        if (install_gamesd_plugin_by_path(paths[i]) >= 0)
            res = 0;
    }

    int ret = write_file(GAMMESD_INSTALL_PATH, (void *)&_binary_res_gamesd_skprx_start, (int)&_binary_res_gamesd_skprx_size);
    if (ret < 0)
        res = ret;

    return res;
}